#include <nana/gui.hpp>
#include <nana/gui/widgets/form.hpp>
#include <nana/gui/widgets/button.hpp>
#include <nana/gui/widgets/label.hpp>
#include <nana/gui/widgets/combox.hpp>
#include <nana/gui/widgets/textbox.hpp>

#include <iostream>
#include <thread>

#include "template_instantiation/sequence_generator.inst.hpp"
#include "template_instantiation/sorting_algorithm.inst.hpp"
#include "template_instantiation/vector.inst.hpp"

#include "animation.hpp"

#include <kerbal/random/linear_congruential_engine.hpp>
#include <kerbal/random/mersenne_twister_engine.hpp>
#include "polymorphic_random_engine.hpp"

#include <kerbal/compare/basic_compare.hpp>
#include <kerbal/container/vector.hpp>


using namespace std::chrono_literals;


struct sorting_algorithms_module
{
		nana::label label;
		int id = 0;
		nana::combox combox;

		sorting_algorithms_module(nana::form & parent) :
				label(parent, "sorting algorithm"),
				combox(parent)
		{
			for (const auto & e : sorting_algorithms) {
				combox.push_back(e.first);
			}
			combox.events().selected([this]() {
				id = static_cast<int>(combox.option());
			});
			combox.option(id);
		}

		void bind_place(nana::place & place)
		{
			place["al"] << label;
			place["a"] << combox;
		}

		static inline constexpr
		std::pair<
			const char *,
			void(*)(Iter first, Iter last, BinaryPredict cmp)
		>
		const sorting_algorithms[]
			 = {
				{"std::sort", std::sort},
				{"std::stable_sort", std::stable_sort},
				{"std_parallel_sort", std_parallel_sort_wrapper},
				{"std_parallel_stable_sort", std_parallel_stable_sort_wrapper},
				{"c qsort", cqsort_wrapper},
				{"bubble sort", kerbal::algorithm::bubble_sort},
				{"selection sort", kerbal::algorithm::selection_sort},
				{"heap sort", kerbal::algorithm::heap_sort},
				{"std::make_heap then sort", std_make_heap_then_sort_wrapper},
				{"merge sort", kerbal::algorithm::merge_sort},
				{"inplace merge sort", kerbal::algorithm::inplace_merge_sort},
				{"stable sort", kerbal::algorithm::stable_sort},
				{"insertion sort", kerbal::algorithm::insertion_sort},
				{"directly insertion sort", kerbal::algorithm::directly_insertion_sort},
				{"quick sort", kerbal::algorithm::quick_sort},
				{"nonrecursive qsort", kerbal::algorithm::nonrecursive_qsort},
				{"intro sort", kerbal::algorithm::intro_sort},
				{"nonrecursive intro sort", kerbal::algorithm::nonrecursive_intro_sort},
				{"shell sort (hibbard sequence)", shell_sort_hibbard_sequence_wrapper},
				{"shell sort (minimun limit hibbard sequence)", shell_sort_minimun_limit_hibbard_sequence_wrapper},
				{"shell sort (reduce by half)", shell_sort_reduce_by_half_wrapper},
				{"shell sort (q)", shell_sort_q_wrapper},
				{"radix sort", radix_sort_wrapper},
//				{"kerbal::omp::quick_sort", omp_quick_sort_wrapper},
//				{"kerbal::omp::merge_sort", kerbal_omp_merge_sort_animation},
//				{"kerbal::omp::shell_sort", omp_shell_sort_wrapper},
#if BOOST_SORT_SUPPORT
				{"boost::block_indirect_sort", boost::sort::block_indirect_sort},
				{"boost::flat_stable_sort", boost::sort::flat_stable_sort},
				{"boost::heap_sort", boost::sort::heap_sort},
				{"boost::pdqsort", boost::sort::pdqsort},
				{"boost::sample_sort", boost::sort::sample_sort},
				{"boost::spinsort", boost::sort::spinsort},
				{"boost::spreadsort", boost_spreadsort_wrapper},
				{"boost::parallel_stable_sort", boost_parallel_stable_sort_wrapper},
#endif
#if GFX_TIMSORT_SUPPORT
				{"gfx::timsort", gfx::timsort},
#endif
				{"freedom implement", freedom_implement},
				{"freedom implement2", freedom_implement2},
		};

};

struct sequence_generator_module
{
		nana::label label;
		int id = 1;
		nana::combox combox;

		sequence_generator_module(nana::form & parent) :
				label(parent, "sequence generator"),
				combox(parent)
		{
			for (const auto & ele : sequence_generator_module::name) {
				combox.push_back(ele);
			}
			combox.events().selected([this]() {
				id = static_cast<int>(combox.option());
			});
			combox.option(id);
		}

		void bind_place(nana::place & place)
		{
			place["seq_gen_lable"] << label;
			place["seq_gen_combox"] << combox;
		}

		static inline constexpr const char * const name[] = {
				"constant",
				"random",
				"sorted",
				"reverse sorted",
				"pipe organ",
				"nearly sorted",
				"few unique",
				"push back",
				"sawtooth",
				"reverse sawtooth",
				"sin",
				"perlin_noise",
		};

		static inline constexpr kerbal::container::vector<int> (* const generator[])(int, polymorphic_random_engine &) = {
				get_constant_sequence,
				get_random_sequence,
				get_sorted_sequence,
				get_reverse_sequence,
				get_pipe_organ,
				get_nearly_sorted_sequence,
				get_few_unique_sequence,
				get_push_back_sequence,
				get_sawtooth_sequence,
				get_reverse_sawtooth_sequence,
				get_sin_wave_sequence,
				get_perlin_noise_sequence,
		};

};


struct random_engine_module
{
		nana::label engine_label;
		int engine_id;
		nana::combox engine_combox;

		nana::label seed_label;
		int seed;
		nana::textbox seed_textbox;

		random_engine_module(nana::form & parent) :
				engine_label(parent, "random engine"),
				engine_id(0),
				engine_combox(parent),
				seed_label(parent, "seed"),
				seed_textbox(parent)
		{
			for (const auto & ele : engine_name) {
				engine_combox.push_back(ele);
			}
			engine_combox.events().selected([this]() {
				engine_id = static_cast<int>(engine_combox.option());
			});
			engine_combox.option(engine_id);

			seed_textbox.set_accept([](wchar_t c) -> bool {
				return c == 8 || c == 127 || std::isdigit(c);
			});
			seed_textbox.events().text_changed([this]() {
				try {
					seed = std::stoi(seed_textbox.caption());
					seed_textbox.bgcolor(nana::colors::white);
				} catch (...) {
					seed_textbox.bgcolor(nana::colors::indian_red);
				}
			});
			seed_textbox.caption("0");
		}

		void bind_place(nana::place & place)
		{
			place["egl"] << engine_label;
			place["eg"] << engine_combox;
			place["seedl"] << seed_label;
			place["seed"] << seed_textbox;
		}

		static inline constexpr const char * const engine_name[] = {
				"mt19937",
				"minstd_rand",
				"minstd_rand0",
		};

};


class main_form :
		public nana::form
{
	private:
		using super = nana::form;

		nana::place place;

		sorting_algorithms_module sortingAlgorithmsModule;
		sequence_generator_module sequenceGeneratorModule;
		random_engine_module randomEngineModule;

		nana::label delay_label;
		std::chrono::microseconds delay = 10000us;
		nana::textbox delay_text_box;

		nana::label scale_label;
		size_t scale = 1500;
		nana::textbox scale_text_box;

		nana::button start_button;

	public:
		main_form() :
				super(nana::API::make_center(500, 200)), place(*this),
				sortingAlgorithmsModule(*this),
				sequenceGeneratorModule(*this),
				randomEngineModule(*this),
				delay_label(*this, "delay (us)"), delay_text_box(*this),
				scale_label(*this, "data scale"), scale_text_box(*this),
				start_button(*this, "start")
		{
			super::caption("visual sort");
			super::bgcolor(nana::colors::white_smoke);
			super::events().destroy([]() {
				std::quick_exit(0);
			});



			delay_text_box.caption(std::to_string(delay.count()));
			delay_text_box.set_accept([](wchar_t c) -> bool {
				return c == 8 || c == 127 || std::isdigit(c);
			});
			delay_text_box.events().text_changed([this]() {
				try {
					int i = std::stoi(delay_text_box.caption());
					delay = std::chrono::microseconds(i);
					delay_text_box.bgcolor(nana::colors::white);
				} catch (...) {
					delay_text_box.bgcolor(nana::colors::indian_red);
				}
			});


			scale_text_box.set_accept([](wchar_t c) -> bool {
				return c == 8 || c == 127 || std::isdigit(c);
			});
			scale_text_box.events().text_changed([this]() {
				try {
					scale = std::stoi(scale_text_box.caption());
					scale_text_box.bgcolor(nana::colors::white);
				} catch (...) {
					scale_text_box.bgcolor(nana::colors::indian_red);
				}
			});
			scale_text_box.caption(std::to_string(scale));


			start_button.events().click([this]() {
				std::unique_ptr<polymorphic_random_engine> polymorphic_random_engine = nullptr;
				switch (randomEngineModule.engine_id) {
					case 0:
						polymorphic_random_engine = std::make_unique<polymorphic_random_engine_wrapper<kerbal::random::mt19937>>();
						break;
					case 1:
						polymorphic_random_engine = std::make_unique<polymorphic_random_engine_wrapper<kerbal::random::minstd_rand>>();
						break;
					case 2:
						polymorphic_random_engine = std::make_unique<polymorphic_random_engine_wrapper<kerbal::random::minstd_rand0>>();
						break;
				}

				polymorphic_random_engine->seed(randomEngineModule.seed);
				kerbal::container::vector<int> v = sequence_generator_module::generator[sequenceGeneratorModule.id]
						(scale, *polymorphic_random_engine);
				std::unique_ptr<animation_base> animation = std::make_unique<animation_base>(delay, sorting_algorithms_module::sorting_algorithms[sortingAlgorithmsModule.id].second);
				animation->caption(
						std::string(sorting_algorithms_module::sorting_algorithms[sortingAlgorithmsModule.id].first) +
						", " +
						sequence_generator_module::name[sequenceGeneratorModule.id] +
						", " +
						randomEngineModule.engine_name[randomEngineModule.engine_id] + " (with seed = " + std::to_string(randomEngineModule.seed) + ")" +
						", " +
						"scale = " + std::to_string(scale)
				);

				std::thread animation_thread([&animation, &v] {
					std::cout << "animation start!" << std::endl;
					animation->start(v.begin(), v.end(), kerbal::compare::less<int>());
					std::cout << "down!" << std::endl;
				});
				animation->show();
				nana::exec();
				animation_thread.join();
			});


			place.div(
					"<width=15>"
					"<vert "
					"<height=15>"
					"<<al width=35%> <a> height=30>"
					"<height=15>"
					"<<seq_gen_lable width=35%> <seq_gen_combox> height=30>"
					"<height=15>"
					"<<egl width=30%> <eg width=30%> <width=30> <seedl> <seed> height=30>"
					"<height=15>"
					"< <<delayl> <delay>> <width=15> <<scalel> <scale>> <width=15> <start> height=30>"
					"<height=15>"
					">"
					"<width=15>"
			);

			sortingAlgorithmsModule.bind_place(place);
			sequenceGeneratorModule.bind_place(place);
			randomEngineModule.bind_place(place);
			place["delayl"] << delay_label;
			place["delay"] << delay_text_box;
			place["scalel"] << scale_label;
			place["scale"] << scale_text_box;
			place["start"] << start_button;


			place.collocate();

			super::show();

		}

};


int main()
{
	main_form mainForm;
	nana::exec();

	return 0;
}
